#include <linux/module.h>	/* Specifically, a module */
#include <linux/kernel.h>	/* We're doing kernel work */
#include <linux/proc_fs.h>	/* Necessary because we use the proc fs */
#include <linux/sched.h>
#include <asm/current.h>
#include <linux/uaccess.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>

#define procfs_name "lfprng"

/**
 * This structure hold information about the /proc file
 *
 */
struct proc_dir_entry *Our_Proc_File;

typedef struct _thread_state_struct {
	pid_t pid;
	pid_t gid;
	unsigned long next_value;
	long seed_increment;
} prng_thread_state;

typedef struct _thread_state_node {
	struct _thread_state_node *next;
	prng_thread_state *thread;
} prng_thread_node;

typedef struct _group_struct {
	pid_t gid;
	int num_threads;
	prng_thread_node *head;
	int orig_seed;
} prng_group_struct;

typedef struct _prng_group_node {
	prng_group_struct *group;
	struct _prng_group_node *next;
} prng_group_node;

prng_group_struct* find_group(pid_t gid);
prng_thread_state* get_thread(prng_group_struct* group, pid_t pid);
void add_group_to_list(prng_group_struct* group);
int get_next_value(int curr_value, int seed_inc);
int construct_thread_group(prng_group_struct *group_leader);
int initialize_seed(prng_group_struct *group_leader, unsigned long seed);


prng_group_node *group_list_head;
static const int a = 764261123;
static const int c = 2147483647;

/*
 */
int procfile_read(char *buffer,
	      char **buffer_location,
	      off_t offset, int buffer_length, int *eof, void *data)
{
	int ret;
	prng_group_struct *curr_group;
	prng_thread_state *curr_thread;
	int rand;
	
	printk(KERN_INFO "The process group %d, pid %d has tried to read.\n", current->tgid, current->pid);

	if(!(curr_group = find_group(current->tgid))) //Group does not exist, does not have a seed
	{
	printk(KERN_INFO "The process group %d has not initialized a seed.\n", current->tgid);
		return 0;
	}
	else
	{
		
		if(!(curr_thread = get_thread(curr_group, current->pid)))
		{
		printk(KERN_INFO "SHOULD NOT SEE THIS.\n");
			return 0;
		}
		else
		{
	printk(KERN_INFO "Thread that we found: pid: %d, next_value: %d\n", curr_thread->pid, curr_thread->next_value);
			rand = curr_thread->next_value;
			curr_thread->next_value = get_next_value(curr_thread->next_value, curr_thread->seed_increment);
		}

		ret = sprintf(buffer, "%d", rand);
	}

	return ret;
}

int procfile_write(struct file *filp,
		const char __user *buff,
		unsigned long len,
		void *data)
{
	int num_threads, ret;
	unsigned long seed;
	const char *kbuff;
	prng_group_struct *curr_group;
printk(KERN_INFO "FILEWRITE BEGIN\n\n---\n");
printk(KERN_INFO "buff: %s, len=%d\n", buff, len);
	//Move buff to kernel space, parse the string into a long
	kbuff = (char *) vmalloc(sizeof(char)*len);
	copy_from_user(kbuff, buff, len);
printk(KERN_INFO "kbuff: %s\n", kbuff);
	seed = simple_strtol(kbuff, &kbuff, 10);

	//Check for group
	pid_t current_gid = current->tgid;

	printk(KERN_INFO "THREAD GROUP %d IS TRYING TO SET ITS SEED: %d\n", current_gid, seed);

	if((curr_group = find_group(current_gid)))
	{
	printk(KERN_INFO "TEST OUT 1\n curr_group: %p\n", curr_group);
		if(!initialize_seed(curr_group, seed))
		{
			printk("WAS NOT ABLE TO SET THE SEED\n");
		}
	}
	else
	{
		//Set up group
		curr_group = (prng_group_struct *) vmalloc(sizeof(prng_group_struct));
		if(!construct_thread_group(curr_group))
		{
			printk("WAS NOT ABLE TO CONSTRUCT THREAD GROUP.\n");
		}
		else
		{
		{
		printk(KERN_INFO "THIS IS CURR_GROUP JUST BEFORE INITIALIZE_SEED IS CALLED: %p.\n", curr_group);
		printk(KERN_INFO "\tGROUP ID: %d\n", curr_group->gid);
			if(!initialize_seed(curr_group, seed))
			{
				printk("WAS NOT ABLE TO SET THE SEED\n");
			}
		}
	}

	return len;
}

int init_module()
{
	//0644 : Read/Write for file owner, read for everyone else
	Our_Proc_File = create_proc_entry(procfs_name, 0666, NULL);
	
	if (Our_Proc_File == NULL) {
		remove_proc_entry(procfs_name, &proc_root);
		printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
		       procfs_name);
		return -ENOMEM;
	}

	Our_Proc_File->read_proc = procfile_read;
	Our_Proc_File->write_proc = procfile_write;
	Our_Proc_File->owner 	 = THIS_MODULE;

	printk(KERN_INFO "/proc/%s created\n", procfs_name);	
	return 0;	/* everything is ok */
}

void  cleanup_module()
{
	remove_proc_entry(procfs_name, &proc_root);
	printk(KERN_INFO "/proc/%s removed\n", procfs_name);
}

prng_group_struct* find_group(pid_t gid)
{
	prng_group_node *curr;
	curr = group_list_head;
if(curr)
{
printk(KERN_INFO "GROUP_LIST_HEAD: %p, id: %d\n", curr, curr->group->gid);
}
	while(curr)
	{
	printk(KERN_INFO "CHECKING GROUP ID: %d\n", curr->group->gid);
		if(curr->group->gid == gid)
			return curr->group;

		curr = curr->next;
	}

	return NULL;
}

prng_thread_state* get_thread(prng_group_struct* group, pid_t pid)
{
	prng_thread_node *curr;
	curr = group->head;

	while(curr)
	{
		if(curr->thread->pid == pid)
			return curr->thread;

		curr = curr->next;
	}

	return NULL;
}

int construct_thread_group(prng_group_struct* group_struct)
{
	int thread_count;
	struct list_head *sibling_list;
	struct task_struct *curr_sibling;
	prng_thread_state *curr_data;
	prng_thread_node *curr_thread_node;

	thread_count = 1;
	group_struct->gid = current->tgid;

	//Create list of threads in this group
	//Initialize and set values for the head of the thread list
	curr_data = vmalloc(sizeof(prng_thread_state));
	curr_data->pid = current->pid;
	curr_data->gid = current->tgid;
	curr_thread_node = vmalloc(sizeof(prng_thread_node));
	curr_thread_node->thread = curr_data;
	curr_thread_node->next = NULL;
	group_struct->head = curr_thread_node;

printk(KERN_INFO "CURR_THREAD_INFO: %p, next: %p, pid: %d\n", curr_thread_node, curr_thread_node->next, curr_thread_node->thread->pid);
	list_for_each(sibling_list, &(current->sibling))
	{
		//Set current sibling
		curr_sibling=list_entry(sibling_list, struct task_struct, sibling);

		//There was another thread, so increment the count
		thread_count++;

		//Create a next node in the thread list, and move curr to it
		curr_thread_node->next = vmalloc(sizeof(prng_thread_node));

		//Initialize the thread data for the new node, set its data
		curr_thread_node->next->thread = vmalloc(sizeof(prng_thread_state));
		curr_thread_node->next->thread->pid = curr_sibling->pid;
		curr_thread_node->next->thread->gid = curr_sibling->tgid;
		curr_thread_node = curr_thread_node->next;
		curr_thread_node->next = NULL;
printk(KERN_INFO "CURR_THREAD_INFO: %p, next: %p, pid: %d\n", curr_thread_node, curr_thread_node->next, curr_thread_node->thread->pid);
	}

	group_struct->num_threads = thread_count;
printk(KERN_INFO "THREAD_COUNT FOR GROUP ID %d: %d\n", group_struct->gid, thread_count);
	add_group_to_list(group_struct);
	
	return thread_count;
}

void add_group_to_list(prng_group_struct* group)
{
	prng_group_node* curr = group_list_head;
	prng_group_node* group_p = (prng_group_node*) vmalloc(sizeof(prng_group_node));
	group_p->group = group;
	group_p->next = NULL;

	if(curr)
	{
		while(curr)
		{
			curr = curr->next;
		}

		curr->next = group_p;
		group_p->next = NULL;
	}
	else
	{
		group_list_head = group_p;
	}
}

int initialize_seed(prng_group_struct* group_member, unsigned long seed)
{
	if(!group_member)
		return 0;

	prng_thread_node *curr;
	unsigned long curr_next_value = seed;
	int seed_increment = group_member->num_threads;

	curr = group_member->head;

	while(curr)
	{
printk(KERN_INFO "CURR THREAD: %p\n", curr);
		curr_next_value = get_next_value(curr_next_value, 1);
		curr->thread->next_value = curr_next_value;
		curr->thread->seed_increment = seed_increment;
		curr = curr->next;
	}
	

	return 1;
}

int get_next_value(int current_value, int seed_inc)
{
	int i, ret = current_value;

	for(i = 0; i < seed_inc; i++)
	{
		ret = (a * ret ) % c;
	}

	return ret;
}
